package com.opennews.openplatform.myspringbootcore.security

import com.opennews.openplatform.myspringbootcore.dto.RequestMap
import org.springframework.stereotype.Component
import java.util.regex.Pattern

@Component
class RequestMapManager(private val requestMapQuery: RequestMapQuery) {
    companion object {
        const val URL_SINGLE_WILDCARD: String = "/*"
        const val URL_DOUBLE_WILDCARD: String = "/**"
        const val DENY_ALL: String = "denyAll"
        const val PERMIT_ALL: String = "permitAll"
        const val IS_AUTHENTICATED_PREFIX: String = "isAuthenticated"
        const val IS_AUTHENTICATED: String = "$IS_AUTHENTICATED_PREFIX()"
        const val IS_FULLY_AUTHENTICATED_PREFIX: String = "isFullyAuthenticated"
        const val IS_FULLY_AUTHENTICATED: String = "$IS_FULLY_AUTHENTICATED_PREFIX()"
        const val HAS_ROLE_PREFIX: String = "hasRole"
        const val HAS_ANY_ROLE_PREFIX: String = "hasAnyRole"
        const val HAS_NO_ROLE_PREFIX: String = "hasNoRole"
        const val HAS_NO_ANY_ROLE_PREFIX: String = "hasNoAnyRole"

        private var cachedRequestMaps: List<RequestMap>? = null
    }

    fun getCachedRequestMaps(): List<RequestMap> {
        if (cachedRequestMaps == null) {
            reloadCachedRequestMaps()
        }

        return cachedRequestMaps ?: emptyList()
    }

    fun reloadCachedRequestMaps() {
        cachedRequestMaps = requestMapQuery.queryAll()
    }

    /**
     * Returns the expression like hasRole('ROLE_ADMIN').
     *
     * @param roleAuthority It is the string of role authority. AKA role name.
     */
    fun hasRole(roleAuthority: String): String {
        return buildExpressionWithRole(HAS_ROLE_PREFIX, roleAuthority)
    }

    /**
     * Returns the expression like hasAnyRole('ROLE_USER','ROLE_ADMIN').
     *
     * @param roleAuthorities It is the list string of role authority. AKA role name.
     */
    fun hasAnyRole(roleAuthorities: List<String>): String {
        return buildExpressionWithRoles(HAS_ANY_ROLE_PREFIX, roleAuthorities)
    }

    /**
     * Returns the expression like hasNoRole('ROLE_ADMIN').
     *
     * @param roleAuthority It is the string of role authority. AKA role name.
     */
    fun hasNoRole(roleAuthority: String): String {
        return buildExpressionWithRole(HAS_NO_ROLE_PREFIX, roleAuthority)
    }

    /**
     * Returns the expression like hasNoAnyRole('ROLE_USER','ROLE_ADMIN').
     *
     * @param roleAuthorities It is the list string of role authority. AKA role name.
     */
    fun hasNoAnyRole(roleAuthorities: List<String>): String {
        return buildExpressionWithRoles(HAS_NO_ANY_ROLE_PREFIX, roleAuthorities)
    }

    /**
     * Extracts role names from request map config attribute.
     * Example: hasRole('admin'), hasAnyRole('reporter', 'planner').
     */
    fun extractRoleNames(configAttribute: String): List<String> {
        val roleNames = mutableListOf<String>()

        // This would match the role names, like 'admin' or 'reporter', 'planner'.
        // Regular expression with string concatenation for the prefixes.
        val regex = "(?:$HAS_ROLE_PREFIX|$HAS_ANY_ROLE_PREFIX|$HAS_NO_ROLE_PREFIX|$HAS_NO_ANY_ROLE_PREFIX)\\((.+)\\)"

        // Creates a Pattern object
        val pattern = Pattern.compile(regex)

        // Creates a matcher object
        val matcher = pattern.matcher(configAttribute)

        // If match found.
        if (matcher.find()) {
            // Gets the matched roles string (inside the parentheses)
            var matchedRoles = matcher.group(1)

            // Removes all single quotes and double quotes
            matchedRoles = matchedRoles.replace("'", "").replace("\"", "")

            // Splits by comma, trims each role.
            val splitedRoles = matchedRoles.split(",").map { it.trim() }

            // Adds it to the roleNames list
            roleNames.addAll(splitedRoles)
        }

        return roleNames
    }

    /**
     * Checks if security request map configAttribute contains any role in roleNames.
     */
    fun containsAnyRoleInConfigAttribute(roleNames: List<String>, configAttribute: String): Boolean {
        return containsAnyRoleInConfigRoleNames(roleNames, extractRoleNames(configAttribute))
    }

    /**
     * Returns the expression like hasAnyRole('ROLE_USER','ROLE_ADMIN').
     *
     * @param prefix:         String of expression prefix. Like hasRole, hasNoRole.
     * @param roleAuthorities It is the list string of role authority. AKA role name.
     */
    private fun buildExpressionWithRoles(prefix: String, roleAuthorities: List<String>): String {
        // Using StringBuilder for efficient string concatenation.
        val result = StringBuilder(prefix).append("(")

        // Appends each roleAuthority to result.
        for (roleAuthority in roleAuthorities) {
            result.append("'").append(roleAuthority).append("',")
        }

        // Removes the last comma and close with a bracket
        if (roleAuthorities.isNotEmpty()) {
            // Removes the trailing comma.
            result.setLength(result.length - 1)
        }

        result.append(")")

        return result.toString()
    }

    /**
     * Returns the expression like hasRole('ROLE_ADMIN').
     *
     * @param prefix:       String of expression prefix. Like hasRole, hasNoRole.
     * @param roleAuthority It is the string of role authority. AKA role name.
     */
    private fun buildExpressionWithRole(prefix: String, roleAuthority: String): String {
        return "%s('%s)".format(prefix, roleAuthority)
    }

    /**
     * Checks if configuredRoleNames contains any role in roleNames.
     */
    private fun containsAnyRoleInConfigRoleNames(roleNames: List<String>, configRoleNames: List<String>): Boolean {
        return roleNames.any { roleName ->
            configRoleNames.any { configRoleName ->
                roleName.equals(configRoleName, ignoreCase = true)
            }
        }
    }
}